const mysql = require("mysql2");
const { wxDatabase, vueDB } = require("../conf/db.config");

/**
 * 数据库连接池
 * 初始化时，将一定数量（数量受最小连接数制约）的数据库连接存放到数据库连接池中，不管这些数据库连接是否被使用，连接池一直要存放这么多的连接数量。连接池的最大数据库连接数量限制了连接池最多能同时拥有的连接数，如果超过最大连接数时，请求将会被添加到等待队列中。
 * 作用：
 * 负责分配、管理和释放数据库连接，它允许应用程序重复使用一个现有的数据库连接，而不是再重新建立一个，从而提高对数据库操作的性能。
 */
const pool = mysql.createPool({
  host: vueDB.host, // 主机名称
  port: 3306, // 数据库的端口号，默认 3306
  user: vueDB.user, // 访问数据库的用户名
  password: vueDB.password, // 用户名对应的密码
  database: vueDB.database, // 访问数据库的名字
  waitForConnections: true,
  connectionLimit: 10, // 最大连接数，默认 10
  queueLimit: 0,
  multipleStatements: true, // 是否允许执行多条查询语句，默认 false
  connectTimeout: 10000, // 默认 10000，单位 ms
  debug: false, // 默认 false
});

pool.on("release", (conn) => {
  console.log("Connection %d released", conn.threadId);
});

module.exports = {
  handleToQuery: (sql, data = false) => {
    const connection = mysql.createConnection({
      host: vueDB.host,
      user: vueDB.user,
      database: vueDB.database,
    });

    /**
     * @method query()
     * @param sql 第一个参数，一个 SQL 语句。使用占位符时，会使用第二个参数（多个占位符，则为数组，第一个占位符对应数组中第一个值，依次类推）中的数据替换占位符
     * @param data 第二个参数，可以是一个数组，可以是其他类型值，也可以省略（此时则是一个回调函数），默认值 false
     * @param 第三个参数，一个回调函数
     *
     * @callback
     * @param error query() 执行错误返回
     * @param data query() 执行成功返回
     *
     * @example
     * query("SELECT * FROM user WHERE name = ? AND id > ?", ["zhang", "2"], (err, results, fields) => {});
     * 此时 SQL 则会被 query() 方法解析成 SELECT * FROM user WHERE name = 'zhang' AND id > 2
     *
     * 注意：为了防止用户利用 SQL 的漏洞，建议使用占位符，而不应该使用字符串拼接，如 const sql = "SELECT * FROM user WHERE name =" + username;
     */
    return new Promise((resolve, reject) => {
      connection.connect((error) => {
        if (error) {
          reject(error);
        } else {
          connection.query(sql, data, (err, results, fields) => {
            if (err) {
              reject(err);
            } else {
              resolve({ results, fields });
            }
          });

          /**
           * 关闭连接
           * 在查询完数据库后，需要通过 end() 方法关闭连接。如果连接一直打开，首先会浪费不必要的系统资源；其次，数据库的连接数量有限制，如果达到上限时，会出现后续连接不上的情况。
           * 由于在node中的网路请求是异步的，所以数据库查询操作也是异步的，通过一个任务队列来依次执行，所以end()并不会直接关闭，而是将存在任务队列中的任务都发送出去后再关闭。
           */
          connection.end();
        }
      });
    });
  },

  query: (sql, data = false) => {
    return new Promise((resolve, reject) => {
      /**
       * 从连接池中取出一个实例建立连接，然后执行 sql 语句
       * 注意：
       * 回调函数要符合 Node.js 错误优先的原则，即回调函数第一个参数为 err
       */
      pool.getConnection((err, conn) => {
        if (err) {
          reject(err);
        }
        conn.query(sql, data, (err, results, fields) => {
          if (err) {
            reject(err);
          } else {
            resolve(results);
          }
          pool.releaseConnection(conn);
        });

        /**
         * 关闭连接池
         * 一般数据库连接池不需要关闭，如果需要关闭，可以使用 end() 方法，即 pool.end((err) => {});
         * 注意：
         * 1. 关闭连接池前所有队列中的查询任然会执行完成，所以每次关闭的时间都不一样。
         * 2. 一旦 end() 方法被调用了，getConnection 和其他一些获取连接池中连接的方法将不会再被执行。
         */
      });
    });
  },

  // 数据库事务的封装
  // connection.beginTransaction()表示事务的开始。然后执行query封装的sql语句。如果执行过程中有出错。那就执行rollback回滚。如果执行成功就执行commit.
  transaction: (sqlArr) => {
    return new Promise((resolve, reject) => {
      pool.getConnection((err, connection) => {
        if (err) {
          resolve(err);
          return;
        }
        connection.beginTransaction((err) => {
          if (err) {
            reject(err);
          }
          for (let i = 0; i < sqlArr.length; i++) {
            this.query1(sqlArr[i]).catch((err) => {
              connection.rollback(() => {
                reject(err);
              });
            });
          }
          connection.commit((err) => {
            if (err) {
              connection.rollback(() => {
                reject(err);
              });
            }
          });
          console.log("transaction complete");
          resolve("complete");
          connection.release();
        });
      });
    });
  },
};
